﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Utilities;
using System.IO;

namespace teavch.Util.Formatter.SourceCode
{

	internal class jQueryFormatter : CodeFormatter
	{

		private enum TokenType
		{
			Undefined,
			Keyword,
			Identifier,
			StringLiteral,
			CharacterLiteral,
			DisabledText,
			Comment,
			Region
		}

		private class SyntaxWalkerEx : SyntaxWalker
		{
			private SemanticModel semanticModel;
			private Action<TokenType, string> writeDelegate;
			private StringBuilder sb = new StringBuilder();
			internal void DoVisit( SyntaxNode token, SemanticModel semanticModel, 
				Action<TokenType, string> writeDelegate )
			{
				this.semanticModel = semanticModel;
				this.writeDelegate = writeDelegate;
				Visit( token );
			}

			// Handle SyntaxTokens
			public override void VisitToken( SyntaxToken token )
			{
				base.VisitLeadingTrivia( token );

				var isProcessed = false;
				if ( (token.IsKeyword() || token.ValueText == "var") || ( token.Kind== SyntaxKind.IdentifierToken && token.ValueText == "function" )  )
				{
					writeDelegate( TokenType.Keyword, token.GetText() );
					isProcessed = true;
				}
				else
				{
					sb.Append( token.Kind.ToString() + " : " + token.ValueText + " : " + token.GetFullText() + System.Environment.NewLine );

					switch ( token.Kind )
					{
						case SyntaxKind.StringLiteralToken:
							writeDelegate( TokenType.StringLiteral, token.GetText() );
							isProcessed = true;
							break;
						case SyntaxKind.CharacterLiteralToken:
							writeDelegate( TokenType.CharacterLiteral, token.GetText() );
							isProcessed = true;
							break;
						case SyntaxKind.IdentifierToken:
							if ( token.Parent is SimpleNameSyntax )
							{
								// SimpleName is the base type of IdentifierNameSyntax, GenericNameSyntax etc.
								// This handles type names that appear in variable declarations etc.
								// e.g. "TypeName x = a + b;"
								var name = ( SimpleNameSyntax ) token.Parent;
								var semanticInfo = semanticModel.GetSymbolInfo( name );
								if ( semanticInfo.Symbol != null && semanticInfo.Symbol.Kind != SymbolKind.ErrorType )
								{
									switch ( semanticInfo.Symbol.Kind )
									{
										case SymbolKind.NamedType:
											writeDelegate( TokenType.Identifier, token.GetText() );
											isProcessed = true;
											break;
										case SymbolKind.Namespace:
										case SymbolKind.Parameter:
										case SymbolKind.Local:
										case SymbolKind.Field:
										case SymbolKind.Property:
											writeDelegate( TokenType.Undefined, token.GetText() );
											isProcessed = true;
											break;
										default:
											break;
									}
								}
							}
							else if ( token.Parent is TypeDeclarationSyntax )
							{
								// TypeDeclarationSyntax is the base type of ClassDeclarationSyntax etc.
								// This handles type names that appear in type declarations
								// e.g. "class TypeName { }"
								var name = ( TypeDeclarationSyntax ) token.Parent;
								var symbol = semanticModel.GetDeclaredSymbol( name );
								if ( symbol != null && symbol.Kind != SymbolKind.ErrorType )
								{
									switch ( symbol.Kind )
									{
										case SymbolKind.NamedType:
											writeDelegate( TokenType.Identifier, token.GetText() );
											isProcessed = true;
											break;
									}
								}
							}
							break;
					}
				}

				if ( !isProcessed )
				{
					try
					{
						HandleSpecialCaseIdentifiers( token );
					}
					catch
					{ }
				}

				base.VisitTrailingTrivia( token );
			}

			/*
			Idea to use Roslyn and VisiteTlem action was taken from Matlus
			http://www.matlus.com/c-to-html-syntax-highlighter-using-roslyn/
			*/
			private void HandleSpecialCaseIdentifiers( SyntaxToken token )
			{
				switch ( token.Kind )
				{
					// Special cases that are not handled because there is no semantic context/model that can truely identify identifiers.
					case SyntaxKind.IdentifierToken:
						if ( ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.Parameter )
							|| ( token.Parent.Kind == SyntaxKind.EnumDeclaration )
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.Attribute )
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.CatchDeclaration )
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.ObjectCreationExpression )
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.ForEachStatement && !( token.GetNextToken().Kind == SyntaxKind.CloseParenToken ) )
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Parent.Kind == SyntaxKind.CaseSwitchLabel && !( token.GetPreviousToken().Kind == SyntaxKind.DotToken ) )
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.MethodDeclaration )
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.CastExpression )
							//e.g. "private static readonly HashSet patternHashSet = new HashSet();" the first HashSet in this case
							|| ( token.Parent.Kind == SyntaxKind.GenericName && token.Parent.Parent.Kind == SyntaxKind.VariableDeclaration )
							//e.g. "private static readonly HashSet patternHashSet = new HashSet();" the second HashSet in this case
							|| ( token.Parent.Kind == SyntaxKind.GenericName && token.Parent.Parent.Kind == SyntaxKind.ObjectCreationExpression )
							//e.g. "public sealed class BuilderRouteHandler : IRouteHandler" IRouteHandler in this case
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.BaseList )
							//e.g. "Type baseBuilderType = typeof(BaseBuilder);" BaseBuilder in this case
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Parent.Parent.Kind == SyntaxKind.TypeOfExpression )
							// e.g. "private DbProviderFactory dbProviderFactory;" OR "DbConnection connection = dbProviderFactory.CreateConnection();"
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.VariableDeclaration )
							// e.g. "DbTypes = new Dictionary();" DbType in this case
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.TypeArgumentList )
							// e.g. "DbTypes.Add("int", DbType.Int32);" DbType in this case
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.MemberAccessExpression && token.Parent.Parent.Parent.Kind == SyntaxKind.Argument && !( token.GetPreviousToken().Kind == SyntaxKind.DotToken || Char.IsLower( token.GetText()[ 0 ] ) ) )
							// e.g. "schemaCommand.CommandType = CommandType.Text;" CommandType in this case
							|| ( token.Parent.Kind == SyntaxKind.IdentifierName && token.Parent.Parent.Kind == SyntaxKind.MemberAccessExpression && !( token.GetPreviousToken().Kind == SyntaxKind.DotToken || Char.IsLower( token.GetText()[ 0 ] ) ) )
							)
						{
							writeDelegate( TokenType.Identifier, token.GetText() );
						}
						else
						{
							if ( token.GetText() == "HashSet" )
							{
							}
							writeDelegate( TokenType.Undefined, token.GetText() );
						}
						break;
					default:
						writeDelegate( TokenType.Undefined, token.GetText() );
						break;
				}
			}

			// Handle SyntaxTrivia
			public override void VisitTrivia( SyntaxTrivia trivia )
			{
				switch ( trivia.Kind )
				{
					case SyntaxKind.MultiLineCommentTrivia:
					case SyntaxKind.SingleLineCommentTrivia:
						writeDelegate( TokenType.Comment, trivia.GetText() );
						break;
					case SyntaxKind.DisabledTextTrivia:
						writeDelegate( TokenType.DisabledText, trivia.GetText() );
						break;
					case SyntaxKind.DocumentationComment:
						writeDelegate( TokenType.Comment, trivia.GetText() );
						break;
					case SyntaxKind.RegionDirective:
					case SyntaxKind.EndRegionDirective:
						writeDelegate( TokenType.Region, trivia.GetText() );
						break;
					default:
						writeDelegate( TokenType.Undefined, trivia.GetText() );
						break;
				}
				base.VisitTrivia( trivia );
			}
		}

		protected override StringBuilder Format( string input, bool quotedIdenfifiers, bool htmlBased )
		{
			StringBuilder sb = this.FormatNow( input, quotedIdenfifiers, htmlBased );
			return new StringBuilder( "<div class=\"divCsCode\" ><code>" + sb.ToString() + "</code></div>" );
		}

		private StringBuilder FormatNow( string input, bool quotedIdenfifiers, bool htmlBased )
		{
			var syntaxTree = SyntaxTree.ParseCompilationUnit( input );
			
			var semanticModel = Compilation.Create(
				outputName: "jQueryToHtmlSyntaxHighlighterCompilation",
				syntaxTrees: new[] { syntaxTree },
				references: new[] { new AssemblyFileReference( typeof( object ).Assembly.Location ) } ).GetSemanticModel( syntaxTree );

			var syntaxWalker = new SyntaxWalkerEx();

			var htmlBuilder = new StringBuilder();
			syntaxWalker.DoVisit( syntaxTree.GetRoot(), semanticModel, ( tk, text ) =>
			{
				text = text.Replace( "\r\n", "<br />" ).Replace( "\n", "<br />" ).Replace( "\t", "&nbsp;&nbsp;" );
				switch ( tk )
				{
					case TokenType.Undefined:
						htmlBuilder.Append( text );
						break;
					case TokenType.Keyword:
					case TokenType.Identifier:
					case TokenType.StringLiteral:
					case TokenType.CharacterLiteral:
					case TokenType.Comment:
					case TokenType.DisabledText:
					case TokenType.Region:
						//htmlBuilder.Append( "<Cs" + tk.ToString() + ">" + System.Web.HttpUtility.HtmlEncode( text ) + "</Cs" + tk.ToString() + ">" );
						htmlBuilder.Append( "<span class=\"Cs" + tk.ToString() + "\">" + System.Web.HttpUtility.HtmlEncode( text ) + "</span>" );
						break;
					default:
						break;
				}
			} );
			return htmlBuilder;
		}

		protected override string[] GetTokens()
		{
			throw new NotImplementedException();
		}
	}

}
